日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Security OAuth2 单点登录和登出

發(fā)布時間:2024/1/1 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Security OAuth2 单点登录和登出 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

    • 1. 單點登錄
      • 1.1 使用內(nèi)存保存客戶端和用戶信息
        • 1.1.1 認證中心 auth-server
        • 1.1.2 子系統(tǒng) service-1
        • 1.1.3 測試
      • 1.2 使用數(shù)據(jù)庫保存客戶端和用戶信息
      • 1.3 單點登錄流程
        • 1.2.1 請求授權(quán)碼,判斷未登錄,重定向登錄頁
        • 1.2.2 登錄成功,重定向繼續(xù)請求授權(quán)碼,未被資源所有者批準,返回批準頁面
        • 1.2.3 資源所有者批準,重定向返回授權(quán)碼
        • 1.2.4 客戶端獲取到授權(quán)碼,請求Token
        • 1.2.5 獲取到Token,重定向 /user
      • 1.3 JWT Token
        • 1.3.1 資源服務(wù)器未添加tokenServices
        • 1.3.2 資源服務(wù)器添加tokenServices
    • 2. 單點登出
    • 3. 總結(jié)

Spring Security OAuth 最新官方已經(jīng)不再維護,以下內(nèi)容只用于學習記錄。

GitHub:shpunishment/spring-security-oauth2-demo

1. 單點登錄

單點登錄即有多個子系統(tǒng),有一個認證中心。當訪問其中任意一個子系統(tǒng)時,如果發(fā)現(xiàn)未登錄,就跳到認證中心進行登錄,登錄完成后再跳回該子系統(tǒng)。此時訪問其他子系統(tǒng)時,就已經(jīng)是登錄狀態(tài)了。登出統(tǒng)一從認證中心登出,登出后各個子系統(tǒng)就無法訪問了,需要再次登錄。

Spring Security OAuth 建立在Spring Security 之上,所以大部分配置還是在Security中,Security完成對用戶的認證和授權(quán),OAuth完成單點登錄。

Spring Security OAuth 的單點登錄主要靠@EnableOAuth2Sso實現(xiàn),簡化了從資源服務(wù)器到認證授權(quán)服務(wù)器的SSO流程,并使用授權(quán)碼方式獲取。

1.1 使用內(nèi)存保存客戶端和用戶信息

1.1.1 認證中心 auth-server

添加依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</artifactId><version>2.3.8.RELEASE</version> </dependency> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--spring2.0集成redis所需common-pool2--> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.4.2</version> </dependency> <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version> </dependency>

application.yml

server:port: 8000servlet:context-path: /auth-serversession:cookie:name: oauth-auth-serverspring:redis:# Redis默認情況下有16個分片,這里配置具體使用的分片,默認是0database: 0host: localhostport: 6379# 連接密碼(默認為空)password:# 連接超時時間(毫秒)timeout: 10000mslettuce:pool:# 連接池最大連接數(shù)(使用負值表示沒有限制) 默認 8max-active: 8# 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1max-wait: -1# 連接池中的最大空閑連接 默認 8max-idle: 8# 連接池中的最小空閑連接 默認 0min-idle: 0

添加授權(quán)服務(wù)器配置,主要令牌路徑的安全性,客戶端詳情和令牌存儲。

這里配置了一個客戶端,支持授權(quán)碼模式和刷新Token,并且將Token存在Redis中。

@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {private static final String RESOURCE_ID = "resource-1";@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate RedisConnectionFactory redisConnectionFactory;/*** 配置授權(quán)服務(wù)器的安全性,令牌端點的安全約束** @param security* @throws Exception*/@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) throws Exception {security// 開啟 /oauth/check_token.tokenKeyAccess("permitAll()")// 開啟 /oauth/token_key.checkTokenAccess("isAuthenticated()")// 允許表單認證// 如果配置,且url中有client_id和client_secret的,則走 ClientCredentialsTokenEndpointFilter// 如果沒有配置,但是url中沒有client_id和client_secret的,走basic認證保護.allowFormAuthenticationForClients();}/*** 配置客戶端,可存在內(nèi)存和數(shù)據(jù)庫中** @param clients* @throws Exception*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("client_1").resourceIds(RESOURCE_ID).authorizedGrantTypes("authorization_code", "refresh_token").scopes("read").authorities("client").secret(passwordEncoder.encode("123456"))// 必須添加,會和請求時重定向地址匹配.redirectUris("http://localhost:8001/service1/login")// 自動批準,在登錄成功后不會跳到批準頁面,讓資源所有者批準//.autoApprove(true);}/**** 配置授權(quán)服務(wù)器端點的非安全功能,例如令牌存儲,令牌自定義,用戶批準和授予類型** @param endpoints* @throws Exception*/@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints// 令牌存在redis.tokenStore(tokenStore());}/*** 配置redis,使用redis存token* @return*/@Beanpublic TokenStore tokenStore(){return new RedisTokenStore(redisConnectionFactory);} }

添加資源服務(wù)器配置,主要配置資源id和需要Token驗證的url

對于相同的url,如果二者都配置了驗證,則優(yōu)先進入ResourceServerConfigurerAdapter,會被 OAuth2AuthenticationProcessingFilter 處理,進行token驗證;而不會進行WebSecurityConfigurerAdapter 的表單認證等。

@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {private static final String RESOURCE_ID = "resource-1";/*** 添加特定于資源服務(wù)器的屬性** @param resources*/@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId(RESOURCE_ID);}/*** 使用此配置安全資源的訪問規(guī)則,配置需要token驗證的url。 默認情況下,所有不在"/oauth/**"中的資源都受到保護。** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {// 只有 /security/getUserInfo 需要token驗證http.requestMatchers().antMatchers("/security/getUserInfo").and().authorizeRequests().anyRequest().authenticated();} }

security配置,用戶數(shù)據(jù),自定義登錄頁,成功失敗Handler,session,配置非受保護URL等。

這里添加了兩個用戶以及登錄頁等配置。

@Configuration public class ServerWebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 認證管理器配置,用于信息獲取來源(UserDetails)以及密碼校驗規(guī)則(PasswordEncoder)** @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth// 使用內(nèi)存認證,在內(nèi)存中保存兩個用戶.inMemoryAuthentication().passwordEncoder(passwordEncoder())// admin 擁有ADMIN和USER的權(quán)限.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN", "USER").and()// user 擁有USER的權(quán)限.withUser("user").password(passwordEncoder().encode("user")).roles("USER");}/*** 核心過濾器配置,更多使用ignoring()用來忽略對靜態(tài)資源的控制* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/static/js/**");}/*** 安全過濾器鏈配置,自定義安全訪問策略* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// /login 和 /oauth/authorize 路徑配置為不需要任何身份驗證,其他所有路徑必須經(jīng)過驗證.antMatchers("/login", "/oauth/authorize").permitAll()// 其他請求都需要已認證.anyRequest().authenticated().and()// 使用表單登錄.formLogin()// 自定義username 和password參數(shù).usernameParameter("login_username").passwordParameter("login_password")// 自定義登錄頁地址.loginPage("/loginPage")// 驗證表單的地址,由過濾器 UsernamePasswordAuthenticationFilter 攔截處理.loginProcessingUrl("/login").permitAll().and().csrf().disable();}@Beanpublic static BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();} }

獲取當前用戶信息,供客戶端獲取

@RestController @RequestMapping("/security") public class SecurityController {@GetMapping("/getUserInfo")@ResponseBodypublic Principal getUserInfo(Principal principal) {return principal;} }

1.1.2 子系統(tǒng) service-1

添加依賴

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency><dependency><groupId>org.springframework.security.oauth.boot</groupId><artifactId>spring-security-oauth2-autoconfigure</artifactId><version>2.1.13.RELEASE</version> </dependency>

application.yml

server:port: 8001servlet:context-path: /service1session:cookie:name: oauth-service-1security:oauth2:client:clientId: client_1clientSecret: 123456# 獲取訪問令牌的URIaccessTokenUri: http://localhost:8000/auth-server/oauth/token# 將用戶重定向到的授權(quán)URIuserAuthorizationUri: http://localhost:8000/auth-server/oauth/authorizeresource:# 獲取當前用戶詳細信息userInfoUri: http://localhost:8000/auth-server/security/getUserInfo

security配置,如果需要對service-1的url進行控制,需要添加 WebSecurityConfigurerAdapter 配置,可配置子系統(tǒng)中哪些接口需要auth-server的認證,配置非受保護URL等。

@Configuration // @EnableOAuth2Sso 注解 在繼承 WebSecurityConfigurerAdapter 類的上面時 // 代表著在該子類配置的基礎(chǔ)上增強 OAuth2Sso 相關(guān)配置。 @EnableOAuth2Sso @EnableGlobalMethodSecurity(prePostEnabled = true) public class ClientWebSecurityConfig extends WebSecurityConfigurerAdapter {/*** 安全過濾器鏈配置,自定義安全訪問策略。可配置客戶端不受保護的資源** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {http.antMatcher("/**").authorizeRequests()// 訪問 / /home 不用認證.antMatchers("/", "/home").permitAll().anyRequest().authenticated().and()// 權(quán)限不足跳轉(zhuǎn) /401.exceptionHandling().accessDeniedPage("/401");}/*** 核心過濾器配置,更多使用ignoring()用來忽略對靜態(tài)資源的控制和過濾微服務(wù)間feign的接口** @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/js/**");} }

客戶端資源服務(wù)器配置,只有 /api/* 需要token驗證

@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter {private static final String RESOURCE_ID = "resource-1";/*** 添加特定于資源服務(wù)器的屬性** @param resources*/@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.resourceId(RESOURCE_ID);}/*** 使用此配置安全資源的訪問規(guī)則,配置需要token驗證的url。 默認情況下,所有不在"/oauth/**"中的資源都受到保護。** @param http* @throws Exception*/@Overridepublic void configure(HttpSecurity http) throws Exception {// /api/* 都需要token驗證,會被 OAuth2AuthenticationProcessingFilter 處理http.requestMatchers().antMatchers("/api/*").and().authorizeRequests().anyRequest().authenticated();} }

service1控制器

@Controller public class Service1Controller {@RequestMapping(path = {"/", "/home"})public ModelAndView home() {return new ModelAndView("home");}@PreAuthorize("hasRole('USER')")@RequestMapping("/user")public ModelAndView user() {return new ModelAndView("user");}@PreAuthorize("hasRole('ADMIN')")@RequestMapping("/admin")public ModelAndView admin() {return new ModelAndView("admin");}/*** 測試 /api/* 是否被資源服務(wù)器攔截,需要token* @return*/@GetMapping("/api/getUserInfo")@ResponseBodypublic Principal getUserInfo() {return SecurityContextHolder.getContext().getAuthentication();}@GetMapping("/api2/getUserInfo")@ResponseBodypublic Principal getUserInfo2() {return SecurityContextHolder.getContext().getAuthentication();} }

1.1.3 測試

service-2根據(jù)service-1復(fù)制一遍。

service-1和service-2不用登錄即可訪問 / /home


訪問 /user 需要認證的資源,會先到auth-server進行認證

資源所有者批準

批準后才能訪問到 /user

service-2的 /user 也可訪問,即實現(xiàn)了單點登錄

訪問 /admin 用戶權(quán)限不足

1.2 使用數(shù)據(jù)庫保存客戶端和用戶信息

只需要修改auth-server中客戶端和用戶信息的獲取方式。

用戶信息部分,修改security配置,參考 Spring Security 使用 中的使用數(shù)據(jù)庫保存用戶信息。

由于將Token等信息存在了Redis中,所以在數(shù)據(jù)庫中只需要保存客戶端信息。修改 AuthorizationServerConfig

@Autowired private DataSource dataSource;@Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.withClientDetails(clientDetails(dataSource)); }/*** 獲取客戶端詳細信息服務(wù),JDBC實現(xiàn)* @return*/ @Bean public ClientDetailsService clientDetails(DataSource dataSource) {return new JdbcClientDetailsService(dataSource); }

添加表和數(shù)據(jù),密碼使用BCrypt加密,數(shù)據(jù)和使用內(nèi)存時一致。

CREATE TABLE `oauth_client_details` (`client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,`resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`access_token_validity` int(11) NULL DEFAULT NULL,`refresh_token_validity` int(11) NULL DEFAULT NULL,`additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,`autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,PRIMARY KEY (`client_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;INSERT INTO `oauth_client_details` VALUES ('client_1', 'resource-1', '$2a$10$TfM5Bisse4ewbmIDfqZcxuYl5dI39/lEzzvkzlxFELKglHQM78FIu', 'read', 'authorization_code,refresh_token', 'http://localhost:8001/service1/login,http://localhost:8002/service2/login', NULL, NULL, NULL, NULL, NULL);


效果與使用內(nèi)存時一致。

1.3 單點登錄流程

打開F12會看到以下重定向過程,可看到大致步驟:

  • 請求授權(quán)碼,判斷未登錄,重定向登錄頁
  • 登錄成功,重定向繼續(xù)請求授權(quán)碼,未被資源所有者批準,返回批準頁面
  • 資源所有者批準,重定向返回授權(quán)碼
  • 客戶端獲取到授權(quán)碼,請求Token
  • 獲取到Token,重定向 /user
  • 1.2.1 請求授權(quán)碼,判斷未登錄,重定向登錄頁

    訪問客戶端受保護資源 localhost:8001/service1/user,未登錄重定向到 localhost:8001/service1/login 進行登錄認證,因為配置了單點登錄@EnableOAuth2Sso,所以單點登錄攔截器會讀取授權(quán)服務(wù)器的配置,發(fā)起獲取授權(quán)碼請求
    http://localhost:8000/auth-server/oauth/authorize?client_id=client_1&redirect_uri=http://localhost:8001/service1/login&response_type=code&state=eEoQJJ

    被auth-server的 AuthorizationEndpoint.authorize() 處理,因為未登錄認證,拋出異常

    if (!(principal instanceof Authentication) || !((Authentication) principal).isAuthenticated()) {throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorization can be completed."); }

    異常在 ExceptionTranslationFilter.doFilter() 中處理

    handleSpringSecurityException(request, response, chain, ase);

    調(diào)用 LoginUrlAuthenticationEntryPoint.commence() 方法,獲取登錄頁地址,并重定向

    redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);

    1.2.2 登錄成功,重定向繼續(xù)請求授權(quán)碼,未被資源所有者批準,返回批準頁面

    在auth-server中用戶密碼由 AbstractAuthenticationProcessingFilter.doFilter() 處理,UsernamePasswordAuthenticationFilter 繼承自 AbstractAuthenticationProcessingFilter,在父類 doFilter() 方法中,會調(diào)用子類實現(xiàn)的 attemptAuthentication 方法,獲取認證信息

    authResult = attemptAuthentication(request, response);

    在 attemptAuthentication() 方法中,將用戶名和密碼封裝成token并認證,并添加額外信息后,進行認證

    this.getAuthenticationManager().authenticate(authRequest);

    getAuthenticationManager() 方法獲取 AuthenticationManager 的實現(xiàn)類 ProviderManager,在 authenticate() 方法中,找到合適的 AuthenticationProvider 處理認證,這里是 DaoAuthenticationProvider,它父類 AbstractUserDetailsAuthenticationProvider 實現(xiàn)了該方法

    result = provider.authenticate(authentication);

    父類會調(diào)用 retrieveUser() 方法檢索用戶,實現(xiàn)在 DaoAuthenticationProvider

    user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);

    這里是從內(nèi)存或數(shù)據(jù)庫中獲取用戶,然后進行密碼校驗,成功后,將信息保存到Authentication,并返回。調(diào)用成功Handler,記住我等等。

    默認登錄成功,會重定向之前請求的地址
    http://localhost:8000/auth-server/oauth/authorize?client_id=client_1&redirect_uri=http://localhost:8001/service1/login&response_type=code&state=eEoQJJ

    再次被auth-server的 AuthorizationEndpoint.authorize() 處理,這時有用戶認證信息,獲取client信息,進行檢查,檢查資源所有者是否批準(客戶端可設(shè)置是否自動批準)

    如果未批準,返回批準頁,請求轉(zhuǎn)發(fā) forward:/oauth/confirm_access

    return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);

    1.2.3 資源所有者批準,重定向返回授權(quán)碼

    用戶批準后,被 AuthorizationEndpoint.approveOrDeny() 方法處理,返回授權(quán)碼,并重定向用戶設(shè)置的地址(/login),并帶上code和state

    return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);

    1.2.4 客戶端獲取到授權(quán)碼,請求Token

    在客戶端 AbstractAuthenticationProcessingFilter 中處理

    authResult = attemptAuthentication(request, response);

    由子類 OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication() 處理,判斷token是否為空

    accessToken = restTemplate.getAccessToken();

    如果為空,在 AuthorizationCodeAccessTokenProvider.obtainAccessToken() 方法中,獲取返回的授權(quán)碼,向auth-server請求Token

    return retrieveToken(request, resource, getParametersForTokenRequest(resource, request),getHeadersForTokenRequest(request));

    在auth-server中 TokenEndpoint.getAccessToken() 方法獲取token,進行客戶端校驗后生成token并返回

    OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);

    1.2.5 獲取到Token,重定向 /user

    回到在客戶端 OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication() 中,獲取到token后,帶上token,向auth-server請求用戶信息。
    默認Token是使用uuid,生成用于認證的token和刷新的Token。認證Token默認12小時過期,刷新的Token默認30天過期。

    OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue());

    在auth-server 被 OAuth2AuthenticationProcessingFilter 處理,從頭部獲取并驗證token后,完成該請求。

    客戶端獲取到用戶信息,在客戶端重新完成登錄的流程,最后在默認的登錄成功Handler中獲取到重定向地址(即 /user),并重定向。

    1.3 JWT Token

    1.3.1 資源服務(wù)器未添加tokenServices

    只需要修改auth-server中授權(quán)服務(wù)器。

    添加依賴

    <dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>1.0.11.RELEASE</version> </dependency>

    自定義生成token攜帶的信息

    @Component public class CustomTokenEnhancer implements TokenEnhancer {@Overridepublic OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {final Map<String, Object> additionalInfo = new HashMap<>(2);UserDetails user = (UserDetails) authentication.getUserAuthentication().getPrincipal();additionalInfo.put("userName", user.getUsername());additionalInfo.put("authorities", user.getAuthorities());((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);return accessToken;} }

    修改 AuthorizationServerConfig

    @Autowired private CustomTokenEnhancer customTokenEnhancer;@Autowired private AuthenticationManager authenticationManager;@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {// token增強配置TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, jwtAccessTokenConverter()));endpoints// 令牌存在redis.tokenStore(tokenStore()).tokenEnhancer(tokenEnhancerChain)// 密碼授權(quán)方式時需要.authenticationManager(authenticationManager)// /oauth/token 運行g(shù)et和post.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); }/*** 用來生成token的轉(zhuǎn)換器* @return*/ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();// 對稱加密,設(shè)置簽名,使用下面這個值作為密鑰jwtAccessTokenConverter.setSigningKey("oauth");return jwtAccessTokenConverter; }

    添加客戶端2,支持密碼授權(quán)方式

    INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('client_2', 'resource-1', '$2a$10$TfM5Bisse4ewbmIDfqZcxuYl5dI39/lEzzvkzlxFELKglHQM78FIu', 'read', 'password,refresh_token', NULL, NULL, NULL, NULL, NULL, NULL);

    測試
    使用密碼模式獲取token

    使用token獲請求資源服務(wù)器保護的接口
    流程
    在auth-server的 TokenEndpoint 中驗證信息并獲取token。然后帶著token請求,在service-1中被 OAuth2AuthenticationProcessingFilter 處理,doFilter() 方法會提取并驗證token。

    按上面的配置,并沒有在資源服務(wù)器中配置tokenServices

    Authentication authResult = authenticationManager.authenticate(authentication);

    所以在加載 Authentication 的時候,tokenServices 為 UserInfoTokenServices,就會調(diào)用配置的 userInfoUri 去auth-server獲取用戶信息

    OAuth2Authentication auth = tokenServices.loadAuthentication(token);

    1.3.2 資源服務(wù)器添加tokenServices

    auth-server
    修改ResourceServerConfig

    @Autowired private TokenStore tokenStore;@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception {DefaultTokenServices defaultTokenServices = new DefaultTokenServices();defaultTokenServices.setTokenStore(tokenStore);resources.resourceId(RESOURCE_ID).tokenServices(defaultTokenServices); }

    service-1
    添加依賴

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency><!--spring2.0集成redis所需common-pool2--> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.4.2</version> </dependency>

    修改application.yml

    spring:redis:# Redis默認情況下有16個分片,這里配置具體使用的分片,默認是0database: 0host: localhostport: 6379# 連接密碼(默認為空)password:# 連接超時時間(毫秒)timeout: 10000mslettuce:pool:# 連接池最大連接數(shù)(使用負值表示沒有限制) 默認 8max-active: 8# 連接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1max-wait: -1# 連接池中的最大空閑連接 默認 8max-idle: 8# 連接池中的最小空閑連接 默認 0min-idle: 0

    修改 ResourceServerConfig

    @Autowired private RedisConnectionFactory redisConnectionFactory;@Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception {DefaultTokenServices defaultTokenServices = new DefaultTokenServices();defaultTokenServices.setTokenStore(tokenStore());resources.resourceId(RESOURCE_ID).tokenServices(defaultTokenServices); }/*** 配置redis,使用redis存token* @return*/ @Bean public TokenStore tokenStore(){return new RedisTokenStore(redisConnectionFactory); }

    流程
    在auth-server的 TokenEndpoint 中驗證信息并獲取token。然后帶著token請求,在service-1中被 OAuth2AuthenticationProcessingFilter 處理,doFilter() 方法會提取并驗證token。

    按上面的配置,并沒有在資源服務(wù)器中配置tokenServices

    Authentication authResult = authenticationManager.authenticate(authentication);

    所以在加載 Authentication 的時候,tokenServices 為 DefaultTokenServices,再加上有UserDetails的實現(xiàn)類,可以解析,就不用在調(diào)用auth-server

    OAuth2Authentication auth = tokenServices.loadAuthentication(token);

    2. 單點登出

    這里除了部分的資源服務(wù)器中配置的api需要token驗證,其他還是依賴于Spring Security的認證。而Spring Security是使用Cookie和Session的記錄用戶。所以可以將認證中心和各個子系統(tǒng)的Cookie設(shè)置在同一路徑下,在認證中心登出時,將Cookie一并刪除,實現(xiàn)認證中心和各個子系統(tǒng)的登出。各子系統(tǒng)需要知道認證中心的登出地址。在這里是http://localhost:8000/auth-server/logout。

    修改認證中心和各個子系統(tǒng)的Cookie路徑,測試發(fā)現(xiàn),放在 / 下才可實現(xiàn)

    server:servlet:session:cookie:path: /

    在auth-server添加登出成功的Handler

    @Component public class CustomLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {// 將子系統(tǒng)的cookie刪掉Cookie[] cookies = request.getCookies();if(cookies != null && cookies.length>0){for (Cookie cookie : cookies){cookie.setMaxAge(0);cookie.setPath("/");response.addCookie(cookie);}}super.handle(request, response, authentication);} }

    修改auth-server的ServerWebSecurityConfig,添加logout配置

    @Configuration public class ServerWebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomLogoutSuccessHandler customLogoutSuccessHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http....and()// 默認為 /logout.logout().logoutSuccessHandler(customLogoutSuccessHandler)// 無效會話.invalidateHttpSession(true)// 清除身份驗證.clearAuthentication(true).permitAll()...;} }

    當然,使用了OAuth發(fā)放token,應(yīng)該也需要使token失效。

    @Autowired private TokenStore tokenStore;@GetMapping("/revokeToken") public void revokeToken(HttpServletRequest request) {String authHeader = request.getHeader("Authorization");if (authHeader != null) {String tokenValue = authHeader.replace("Bearer", "").trim();OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);tokenStore.removeAccessToken(accessToken);} }

    3. 總結(jié)

  • AuthorizationEndpoint 處理 /oauth/authorize;TokenEndpoint 處理 /oauth/token。

  • @EnableOAuth2Sso 會將資源服務(wù)器標記為OAuth 2.0的客戶端, 它將負責將資源所有者(最終用戶)重定向到用戶必須輸入其憑據(jù)的授權(quán)服務(wù)器。完成后,用戶將被重定向回具有授權(quán)碼的客戶端。然后客戶端通過調(diào)用授權(quán)服務(wù)器獲取授權(quán)代碼并將其交換為訪問令牌。只有在此之后,客戶端才能使用訪問令牌調(diào)用資源服務(wù)器。

  • @EnableResourceServer 意味著所屬的服務(wù)需要訪問令牌才能處理請求。在調(diào)用資源服務(wù)器之前,需要先從授權(quán)服務(wù)器獲取訪問令牌。

  • 在資源服務(wù)器中配置的路徑,都會被 OAuth2AuthenticationProcessingFilter 處理,獲取token。

  • 之前一直在糾結(jié),客戶端獲取到了token,為什么在訪問 /user 的請求頭中并沒有Authorization,亦可請求成功。其實都因為Security。沒有在資源服務(wù)器中配置的路徑,登錄認證成功后并不需要攜帶token,而還是使用Security需要的Cookie和Session。

  • 如果資源服務(wù)器沒有配置tokenService,就會調(diào)用配置的userInfoUri去auth-server獲取用戶信息;如果資源服務(wù)器配置了tokenService,再加上有UserDetails的實現(xiàn)類,可以解析,就不用在調(diào)用auth-server的接口。

  • 參考:
    Spring Security Oauth2和Spring Boot實現(xiàn)單點登錄
    Spring Security Oauth2 單點登錄案例實現(xiàn)和執(zhí)行流程剖析
    Spring Security OAuth2 入門
    Spring security. How to log out user (revoke oauth2 token)
    從零開始的Spring Security Oauth2(一)
    從零開始的Spring Security Oauth2(二)
    從零開始的Spring Security Oauth2(三)
    Spring Security OAuth2 入門
    Spring Security EnableOAuth2Sso注解實現(xiàn)原理
    Spring Security OAuth2 使用Redis存儲token鍵值詳解
    Spring Security OAuth2實現(xiàn)使用JWT
    jwt 官網(wǎng)
    jwt 解碼器

    總結(jié)

    以上是生活随笔為你收集整理的Spring Security OAuth2 单点登录和登出的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。