<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
Spring Security OAuth 最新官方已經不再維護,以下內容只用於學習記錄。
GitHub:shpunishment/spring-security-oauth2-demo
單點登入即有多個子系統,有一個認證中心。當存取其中任意一個子系統時,如果發現未登入,就跳到認證中心進行登入,登入完成後再跳回該子系統。此時存取其他子系統時,就已經是登入狀態了。登出統一從認證中心登出,登出後各個子系統就無法存取了,需要再次登入。
Spring Security OAuth 建立在Spring Security 之上,所以大部分設定還是在Security中,Security完成對使用者的認證和授權,OAuth完成單點登入。
Spring Security OAuth 的單點登入主要靠@EnableOAuth2Sso實現,簡化了從資源伺服器到認證授權伺服器的SSO流程,並使用授權碼方式獲取。
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: 8000 servlet: context-path: /auth-server session: cookie: name: oauth-auth-server spring: redis: # Redis預設情況下有16個分片,這裡設定具體使用的分片,預設是0 database: 0 host: localhost port: 6379 # 連線密碼(預設為空) password: # 連線超時時間(毫秒) timeout: 10000ms lettuce: pool: # 連線池最大連線數(使用負值表示沒有限制) 預設 8 max-active: 8 # 連線池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1 max-wait: -1 # 連線池中的最大空閒連線 預設 8 max-idle: 8 # 連線池中的最小空閒連線 預設 0 min-idle: 0
新增授權伺服器設定,主要令牌路徑的安全性,使用者端詳情和令牌儲存。
這裡設定了一個使用者端,支援授權碼模式和重新整理Token,並且將Token存在Redis中。
@Configuration @EnableAuthorizationServer public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { private static final String RESOURCE_ID = "resource-1"; @Autowired private PasswordEncoder passwordEncoder; @Autowired private RedisConnectionFactory redisConnectionFactory; /** * 設定授權伺服器的安全性,令牌端點的安全約束 * * @param security * @throws Exception */ @Override public 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(); } /** * 設定使用者端,可存在記憶體和資料庫中 * * @param clients * @throws Exception */ @Override public 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); } /** * * 設定授權伺服器端點的非安全功能,例如令牌儲存,令牌自定義,使用者批准和授予型別 * * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints // 令牌存在redis .tokenStore(tokenStore()); } /** * 設定redis,使用redis存token * @return */ @Bean public TokenStore tokenStore(){ return new RedisTokenStore(redisConnectionFactory); } }
新增資源伺服器設定,主要設定資源id和需要Token驗證的url
對於相同的url,如果二者都設定了驗證,則優先進入ResourceServerConfigurerAdapter,會被 OAuth2AuthenticationProcessingFilter 處理,進行token驗證;而不會進行WebSecurityConfigurerAdapter 的表單認證等。
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "resource-1"; /** * 新增特定於資源伺服器的屬性 * * @param resources */ @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId(RESOURCE_ID); } /** * 使用此設定安全資源的存取規則,設定需要token驗證的url。 預設情況下,所有不在"/oauth/**"中的資源都受到保護。 * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { // 只有 /security/getUserInfo 需要token驗證 http .requestMatchers().antMatchers("/security/getUserInfo") .and() .authorizeRequests() .anyRequest().authenticated(); } }
security設定,使用者資料,自定義登入頁,成功失敗Handler,session,設定非受保護URL等。
這裡新增了兩個使用者以及登入頁等設定。
@Configuration public class ServerWebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 認證管理器設定,用於資訊獲取來源(UserDetails)以及密碼校驗規則(PasswordEncoder) * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth // 使用記憶體認證,在記憶體中儲存兩個使用者 .inMemoryAuthentication() .passwordEncoder(passwordEncoder()) // admin 擁有ADMIN和USER的許可權 .withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN", "USER") .and() // user 擁有USER的許可權 .withUser("user").password(passwordEncoder().encode("user")).roles("USER"); } /** * 核心過濾器設定,更多使用ignoring()用來忽略對靜態資源的控制 * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/static/js/**"); } /** * 安全過濾器鏈設定,自定義安全存取策略 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() // /login 和 /oauth/authorize 路徑設定為不需要任何身份驗證,其他所有路徑必須經過驗證 .antMatchers("/login", "/oauth/authorize").permitAll() // 其他請求都需要已認證 .anyRequest().authenticated() .and() // 使用表單登入 .formLogin() // 自定義username 和password引數 .usernameParameter("login_username") .passwordParameter("login_password") // 自定義登入頁地址 .loginPage("/loginPage") // 驗證表單的地址,由過濾器 UsernamePasswordAuthenticationFilter 攔截處理 .loginProcessingUrl("/login") .permitAll() .and() .csrf().disable(); } @Bean public static BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
獲取當前使用者資訊,供使用者端獲取
@RestController @RequestMapping("/security") public class SecurityController { @GetMapping("/getUserInfo") @ResponseBody public Principal getUserInfo(Principal principal) { return principal; } }
1.1.2 子系統 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: 8001 servlet: context-path: /service1 session: cookie: name: oauth-service-1 security: oauth2: client: clientId: client_1 clientSecret: 123456 # 獲取存取令牌的URI accessTokenUri: http://localhost:8000/auth-server/oauth/token # 將使用者重定向到的授權URI userAuthorizationUri: http://localhost:8000/auth-server/oauth/authorize resource: # 獲取當前使用者詳細資訊 userInfoUri: http://localhost:8000/auth-server/security/getUserInfo
security設定,如果需要對service-1的url進行控制,需要新增 WebSecurityConfigurerAdapter 設定,可設定子系統中哪些介面需要auth-server的認證,設定非受保護URL等。
@Configuration // @EnableOAuth2Sso 註解 在繼承 WebSecurityConfigurerAdapter 類的上面時 // 代表著在該子類設定的基礎上增強 OAuth2Sso 相關設定。 @EnableOAuth2Sso @EnableGlobalMethodSecurity(prePostEnabled = true) public class ClientWebSecurityConfig extends WebSecurityConfigurerAdapter { /** * 安全過濾器鏈設定,自定義安全存取策略。可設定使用者端不受保護的資源 * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { http .antMatcher("/**") .authorizeRequests() // 存取 / /home 不用認證 .antMatchers("/", "/home").permitAll() .anyRequest().authenticated() .and() // 許可權不足跳轉 /401 .exceptionHandling().accessDeniedPage("/401"); } /** * 核心過濾器設定,更多使用ignoring()用來忽略對靜態資源的控制和過濾微服務間feign的介面 * * @param web * @throws Exception */ @Override public void configure(WebSecurity web) throws Exception { web .ignoring() .antMatchers("/js/**"); } }
使用者端資源伺服器設定,只有 /api/* 需要token驗證
@Configuration @EnableResourceServer public class ResourceServerConfig extends ResourceServerConfigurerAdapter { private static final String RESOURCE_ID = "resource-1"; /** * 新增特定於資源伺服器的屬性 * * @param resources */ @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { resources .resourceId(RESOURCE_ID); } /** * 使用此設定安全資源的存取規則,設定需要token驗證的url。 預設情況下,所有不在"/oauth/**"中的資源都受到保護。 * * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { // /api/* 都需要token驗證,會被 OAuth2AuthenticationProcessingFilter 處理 http .requestMatchers() .antMatchers("/api/*") .and() .authorizeRequests() .anyRequest().authenticated(); } }
service1控制器
@Controllerpublic class Service1Controller {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> @RequestMapping(path = {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->"/", "/home"}) public ModelAndView home() {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> return new ModelAndView("home"); } @PreAuthorize("hasRole('USER')") @RequestMapping("/user") public ModelAndView user() {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> return new ModelAndView("user"); } @PreAuthorize("hasRole('ADMIN')") @RequestMapping("/admin") public ModelAndView admin() {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> return new ModelAndView("admin"); } /** * 測試 /api/* 是否被資源伺服器攔截,需要token * @return */ @GetMapping("/api/getUserInfo") @ResponseBody public Principal getUserInfo() {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> return SecurityContextHolder.getContext().getAuthentication(); } @GetMapping("/api2/getUserInfo") @ResponseBody public Principal getUserInfo2() {<!--{C}%3C!%2D%2D%20%2D%2D%3E--> return SecurityContextHolder.getContext().getAuthentication(); }}@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/* 是否被資源伺服器攔截,需要token * @return */ @GetMapping("/api/getUserInfo") @ResponseBody public Principal getUserInfo() { return SecurityContextHolder.getContext().getAuthentication(); } @GetMapping("/api2/getUserInfo") @ResponseBody public Principal getUserInfo2() { return SecurityContextHolder.getContext().getAuthentication(); } }
1.1.3 測試
service-2根據service-1複製一遍。
service-1和service-2不用登入即可存取 / /home
存取 /user 需要認證的資源,會先到auth-server進行認證
資源所有者批准
批准後才能存取到 /user
service-2的 /user 也可存取,即實現了單點登入
存取 /admin 使用者許可權不足
只需要修改auth-server中使用者端和使用者資訊的獲取方式。
使用者資訊部分,修改security設定,參考 Spring Security 使用 中的使用資料庫儲存使用者資訊。
由於將Token等資訊存在了Redis中,所以在資料庫中只需要儲存使用者端資訊。修改 AuthorizationServerConfig
@Autowired private DataSource dataSource; @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { clients .withClientDetails(clientDetails(dataSource)); } /** * 獲取使用者端詳細資訊服務,JDBC實現 * @return */ @Bean public ClientDetailsService clientDetails(DataSource dataSource) { return new JdbcClientDetailsService(dataSource); }
新增表和資料,密碼使用BCrypt加密,資料和使用記憶體時一致。
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);
效果與使用記憶體時一致。
開啟F12會看到以下重定向過程,可看到大致步驟:
1.2.1 請求授權碼,判斷未登入,重定向登入頁
存取使用者端受保護資源 localhost:8001/service1/user,未登入重定向到 localhost:8001/service1/login 進行登入認證,因為設定了單點登入@EnableOAuth2Sso,所以單點登入攔截器會讀取授權伺服器的設定,發起獲取授權碼請求
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);
呼叫 LoginUrlAuthenticationEntryPoint.commence() 方法,獲取登入頁地址,並重定向
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
1.2.2 登入成功,重定向繼續請求授權碼,未被資源所有者批准,返回批准頁面
在auth-server中使用者密碼由 AbstractAuthenticationProcessingFilter.doFilter() 處理,UsernamePasswordAuthenticationFilter 繼承自 AbstractAuthenticationProcessingFilter,在父類別 doFilter() 方法中,會呼叫子類實現的 attemptAuthentication 方法,獲取認證資訊
authResult = attemptAuthentication(request, response);
在 attemptAuthentication() 方法中,將使用者名稱和密碼封裝成token並認證,並新增額外資訊後,進行認證
this.getAuthenticationManager().authenticate(authRequest);
getAuthenticationManager() 方法獲取 AuthenticationManager 的實現類 ProviderManager,在 authenticate() 方法中,找到合適的 AuthenticationProvider 處理認證,這裡是 DaoAuthenticationProvider,它父類別 AbstractUserDetailsAuthenticationProvider 實現了該方法
result = provider.authenticate(authentication);
父類別會呼叫 retrieveUser() 方法檢索使用者,實現在 DaoAuthenticationProvider
user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
這裡是從記憶體或資料庫中獲取使用者,然後進行密碼校驗,成功後,將資訊儲存到Authentication,並返回。呼叫成功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資訊,進行檢查,檢查資源所有者是否批准(使用者端可設定是否自動批准)
如果未批准,返回批准頁,請求轉發 forward:/oauth/confirm_access
return getUserApprovalPageResponse(model, authorizationRequest, (Authentication) principal);
1.2.3 資源所有者批准,重定向返回授權碼
使用者批准後,被 AuthorizationEndpoint.approveOrDeny() 方法處理,返回授權碼,並重定向使用者設定的地址(/login),並帶上code和state
return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);
1.2.4 使用者端獲取到授權碼,請求Token
在使用者端 AbstractAuthenticationProcessingFilter 中處理
authResult = attemptAuthentication(request, response);
由子類 OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication() 處理,判斷token是否為空
accessToken = restTemplate.getAccessToken();
如果為空,在 AuthorizationCodeAccessTokenProvider.obtainAccessToken() 方法中,獲取返回的授權碼,向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.1 資源伺服器未新增tokenServices
只需要修改auth-server中授權伺服器。
新增依賴
<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 { @Override public 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) // 密碼授權方式時需要 .authenticationManager(authenticationManager) // /oauth/token 執行get和post .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); } /** * 用來生成token的轉換器 * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter(); // 對稱加密,設定簽名,使用下面這個值作為金鑰 jwtAccessTokenConverter.setSigningKey("oauth"); return jwtAccessTokenConverter; }
新增使用者端2,支援密碼授權方式
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獲請求資源伺服器保護的介面
流程
在auth-server的 TokenEndpoint 中驗證資訊並獲取token。然後帶著token請求,在service-1中被 OAuth2AuthenticationProcessingFilter 處理,doFilter() 方法會提取並驗證token。
按上面的設定,並沒有在資源伺服器中設定tokenServices
Authentication authResult = authenticationManager.authenticate(authentication);
所以在載入 Authentication 的時候,tokenServices 為 UserInfoTokenServices,就會呼叫設定的 userInfoUri 去auth-server獲取使用者資訊
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
1.3.2 資源伺服器新增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個分片,這裡設定具體使用的分片,預設是0 database: 0 host: localhost port: 6379 # 連線密碼(預設為空) password: # 連線超時時間(毫秒) timeout: 10000ms lettuce: pool: # 連線池最大連線數(使用負值表示沒有限制) 預設 8 max-active: 8 # 連線池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1 max-wait: -1 # 連線池中的最大空閒連線 預設 8 max-idle: 8 # 連線池中的最小空閒連線 預設 0 min-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。
按上面的設定,並沒有在資源伺服器中設定tokenServices
Authentication authResult = authenticationManager.authenticate(authentication);
所以在載入 Authentication 的時候,tokenServices 為 DefaultTokenServices,再加上有UserDetails的實現類,可以解析,就不用在呼叫auth-server
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
這裡除了部分的資源伺服器中設定的api需要token驗證,其他還是依賴於Spring Security的認證。而Spring Security是使用Cookie和Session的記錄使用者。所以可以將認證中心和各個子系統的Cookie設定在同一路徑下,在認證中心登出時,將Cookie一併刪除,實現認證中心和各個子系統的登出。各子系統需要知道認證中心的登出地址。在這裡是http://localhost:8000/auth-server/logout。
修改認證中心和各個子系統的Cookie路徑,測試發現,放在 / 下才可實現
server: servlet: session: cookie: path: /server: servlet: session: cookie: path: /
在auth-server新增登出成功的Handler
@Component public class CustomLogoutSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // 將子系統的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 { @Autowired private CustomLogoutSuccessHandler customLogoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http ... .and() // 預設為 /logout .logout() .logoutSuccessHandler(customLogoutSuccessHandler) // 無效對談 .invalidateHttpSession(true) // 清除身份驗證 .clearAuthentication(true) .permitAll() ...; } }
當然,使用了OAuth發放token,應該也需要使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); } }
Spring Security Oauth2和Spring Boot實現單點登入
Spring Security Oauth2 單點登入案例實現和執行流程剖析
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註解實現原理
Spring Security OAuth2 使用Redis儲存token鍵值詳解
Spring Security OAuth2實現使用JWT
jwt 官網
jwt 解碼器
到此這篇關於SpringSecurity OAuth2單點登入和登出的實現的文章就介紹到這了,更多相關SpringSecurity OAuth2單點登入登出內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45