最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringSecurity OAuth2單點(diǎn)登錄和登出的實(shí)現(xiàn)

 更新時(shí)間:2022年02月09日 11:23:33   作者:shpunishment  
本文主要介紹了SpringSecurity OAuth2單點(diǎn)登錄和登出的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

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

GitHub:shpunishment/spring-security-oauth2-demo

1. 單點(diǎn)登錄

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

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

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

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

1.1.1 認(rèn)證中心 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默認(rèn)情況下有16個(gè)分片,這里配置具體使用的分片,默認(rèn)是0
    database: 0
    host: localhost
    port: 6379
    # 連接密碼(默認(rèn)為空)
    password:
    # 連接超時(shí)時(shí)間(毫秒)
    timeout: 10000ms
    lettuce:
      pool:
        # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制) 默認(rèn) 8
        max-active: 8
        # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) 默認(rèn) -1
        max-wait: -1
        # 連接池中的最大空閑連接 默認(rèn) 8
        max-idle: 8
        # 連接池中的最小空閑連接 默認(rèn) 0
        min-idle: 0

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

這里配置了一個(gè)客戶端,支持授權(quán)碼模式和刷新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;

    /**
     * 配置授權(quán)服務(wù)器的安全性,令牌端點(diǎn)的安全約束
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                // 開啟 /oauth/check_token
                .tokenKeyAccess("permitAll()")
                // 開啟 /oauth/token_key
                .checkTokenAccess("isAuthenticated()")
                // 允許表單認(rèn)證
                // 如果配置,且url中有client_id和client_secret的,則走 ClientCredentialsTokenEndpointFilter
                // 如果沒有配置,但是url中沒有client_id和client_secret的,走basic認(rèn)證保護(hù)
                .allowFormAuthenticationForClients();
    }

    /**
     * 配置客戶端,可存在內(nèi)存和數(shù)據(jù)庫中
     *
     * @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"))
                // 必須添加,會(huì)和請(qǐng)求時(shí)重定向地址匹配
                .redirectUris("http://localhost:8001/service1/login")
                // 自動(dòng)批準(zhǔn),在登錄成功后不會(huì)跳到批準(zhǔn)頁面,讓資源所有者批準(zhǔn)
                //.autoApprove(true);
    }

    /**
     *
     * 配置授權(quán)服務(wù)器端點(diǎn)的非安全功能,例如令牌存儲(chǔ),令牌自定義,用戶批準(zhǔn)和授予類型
     *
     * @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);
    }
}

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

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

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource-1";

    /**
     * 添加特定于資源服務(wù)器的屬性
     *
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .resourceId(RESOURCE_ID);
    }

    /**
     * 使用此配置安全資源的訪問規(guī)則,配置需要token驗(yàn)證的url。 默認(rèn)情況下,所有不在"/oauth/**"中的資源都受到保護(hù)。
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // 只有 /security/getUserInfo 需要token驗(yàn)證
        http
                .requestMatchers().antMatchers("/security/getUserInfo")
                .and()
                .authorizeRequests()
                .anyRequest().authenticated();
    }
} 

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

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

@Configuration
public class ServerWebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 認(rèn)證管理器配置,用于信息獲取來源(UserDetails)以及密碼校驗(yàn)規(guī)則(PasswordEncoder)
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                // 使用內(nèi)存認(rèn)證,在內(nèi)存中保存兩個(gè)用戶
                .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()用來忽略對(duì)靜態(tài)資源的控制
     * @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 路徑配置為不需要任何身份驗(yàn)證,其他所有路徑必須經(jīng)過驗(yàn)證
                .antMatchers("/login", "/oauth/authorize").permitAll()
                // 其他請(qǐng)求都需要已認(rèn)證
                .anyRequest().authenticated()
                .and()
                // 使用表單登錄
                .formLogin()
                // 自定義username 和password參數(shù)
                .usernameParameter("login_username")
                .passwordParameter("login_password")
                // 自定義登錄頁地址
                .loginPage("/loginPage")
                // 驗(yàn)證表單的地址,由過濾器 UsernamePasswordAuthenticationFilter 攔截處理
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf().disable();
    }

    @Bean
    public static BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

獲取當(dāng)前用戶信息,供客戶端獲取

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

auth-server

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: 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
      # 將用戶重定向到的授權(quán)URI
      userAuthorizationUri: http://localhost:8000/auth-server/oauth/authorize
    resource:
      # 獲取當(dāng)前用戶詳細(xì)信息
      userInfoUri: http://localhost:8000/auth-server/security/getUserInfo

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

@Configuration
// @EnableOAuth2Sso 注解 在繼承 WebSecurityConfigurerAdapter 類的上面時(shí)
// 代表著在該子類配置的基礎(chǔ)上增強(qiáng) OAuth2Sso 相關(guān)配置。
@EnableOAuth2Sso
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ClientWebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 安全過濾器鏈配置,自定義安全訪問策略??膳渲每蛻舳瞬皇鼙Wo(hù)的資源
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .antMatcher("/**")
                .authorizeRequests()
                // 訪問 / /home 不用認(rèn)證
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
                // 權(quán)限不足跳轉(zhuǎn) /401
                .exceptionHandling().accessDeniedPage("/401");
    }

    /**
     * 核心過濾器配置,更多使用ignoring()用來忽略對(duì)靜態(tài)資源的控制和過濾微服務(wù)間feign的接口
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
                .ignoring()
                .antMatchers("/js/**");
    }
}

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

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    private static final String RESOURCE_ID = "resource-1";

    /**
     * 添加特定于資源服務(wù)器的屬性
     *
     * @param resources
     */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources
                .resourceId(RESOURCE_ID);
    }

    /**
     * 使用此配置安全資源的訪問規(guī)則,配置需要token驗(yàn)證的url。 默認(rèn)情況下,所有不在"/oauth/**"中的資源都受到保護(hù)。
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        // /api/* 都需要token驗(yàn)證,會(huì)被 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/* 是否被資源服務(wù)器攔截,需要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/* 是否被資源服務(wù)器攔截,需要token
     * @return
     */
    @GetMapping("/api/getUserInfo")
    @ResponseBody
    public Principal getUserInfo() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
    
    @GetMapping("/api2/getUserInfo")
    @ResponseBody
    public Principal getUserInfo2() {
        return SecurityContextHolder.getContext().getAuthentication();
    }
}

service-1

1.1.3 測試

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

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

service-1 home

service-2 /

訪問 /user 需要認(rèn)證的資源,會(huì)先到auth-server進(jìn)行認(rèn)證

auth-server login

資源所有者批準(zhǔn)

auth-server 批準(zhǔn)

批準(zhǔn)后才能訪問到 /user

service-1 user

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

service-1 user

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

service-1 admin

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));
}

/**
 * 獲取客戶端詳細(xì)信息服務(wù),JDBC實(shí)現(xiàn)
 * @return
 */
@Bean
public ClientDetailsService clientDetails(DataSource dataSource) {
    return new JdbcClientDetailsService(dataSource);
}

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

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);

auth-server 數(shù)據(jù)庫

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

1.3 單點(diǎn)登錄流程

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

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

重定向過程

1.2.1 請(qǐng)求授權(quán)碼,判斷未登錄,重定向登錄頁

訪問客戶端受保護(hù)資源 localhost:8001/service1/user,未登錄重定向到 localhost:8001/service1/login 進(jìn)行登錄認(rèn)證,因?yàn)榕渲昧藛吸c(diǎn)登錄@EnableOAuth2Sso,所以單點(diǎn)登錄攔截器會(huì)讀取授權(quán)服務(wù)器的配置,發(fā)起獲取授權(quán)碼請(qǐng)求
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() 處理,因?yàn)槲吹卿浾J(rèn)證,拋出異常

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ù)請(qǐng)求授權(quán)碼,未被資源所有者批準(zhǔn),返回批準(zhǔn)頁面

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

authResult = attemptAuthentication(request, response);

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

this.getAuthenticationManager().authenticate(authRequest);

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

result = provider.authenticate(authentication);

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

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

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

默認(rèn)登錄成功,會(huì)重定向之前請(qǐng)求的地址
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() 處理,這時(shí)有用戶認(rèn)證信息,獲取client信息,進(jìn)行檢查,檢查資源所有者是否批準(zhǔn)(客戶端可設(shè)置是否自動(dòng)批準(zhǔn))

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

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

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

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

return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);

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

在客戶端 AbstractAuthenticationProcessingFilter 中處理

authResult = attemptAuthentication(request, response);

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

accessToken = restTemplate.getAccessToken();

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

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

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

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

1.2.5 獲取到Token,重定向 /user

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

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

在auth-server 被 OAuth2AuthenticationProcessingFilter 處理,從頭部獲取并驗(yàn)證token后,完成該請(qǐng)求。

客戶端獲取到用戶信息,在客戶端重新完成登錄的流程,最后在默認(rèn)的登錄成功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 {
    @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增強(qiáng)配置
    TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
    tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, jwtAccessTokenConverter()));

    endpoints
            // 令牌存在redis
            .tokenStore(tokenStore())
            .tokenEnhancer(tokenEnhancerChain)
            // 密碼授權(quán)方式時(shí)需要
            .authenticationManager(authenticationManager)
            // /oauth/token 運(yùn)行g(shù)et和post
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}

/**
 * 用來生成token的轉(zhuǎn)換器
 * @return
 */
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
   JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
   // 對(duì)稱加密,設(shè)置簽名,使用下面這個(gè)值作為密鑰
   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

使用token獲請(qǐng)求資源服務(wù)器保護(hù)的接口

測試token

流程

在auth-server的 TokenEndpoint 中驗(yàn)證信息并獲取token。然后帶著token請(qǐng)求,在service-1中被 OAuth2AuthenticationProcessingFilter 處理,doFilter() 方法會(huì)提取并驗(yàn)證token。

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

Authentication authResult = authenticationManager.authenticate(authentication);

所以在加載 Authentication 的時(shí)候,tokenServices 為 UserInfoTokenServices,就會(huì)調(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默認(rèn)情況下有16個(gè)分片,這里配置具體使用的分片,默認(rèn)是0
    database: 0
    host: localhost
    port: 6379
    # 連接密碼(默認(rèn)為空)
    password:
    # 連接超時(shí)時(shí)間(毫秒)
    timeout: 10000ms
    lettuce:
      pool:
        # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制) 默認(rèn) 8
        max-active: 8
        # 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制) 默認(rèn) -1
        max-wait: -1
        # 連接池中的最大空閑連接 默認(rèn) 8
        max-idle: 8
        # 連接池中的最小空閑連接 默認(rèn) 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 中驗(yàn)證信息并獲取token。然后帶著token請(qǐng)求,在service-1中被 OAuth2AuthenticationProcessingFilter 處理,doFilter() 方法會(huì)提取并驗(yàn)證token。

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

Authentication authResult = authenticationManager.authenticate(authentication);

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

OAuth2Authentication auth = tokenServices.loadAuthentication(token);

2. 單點(diǎn)登出

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

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

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 {
        // 將子系統(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 {

    @Autowired
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
        		...
                .and()
                // 默認(rèn)為 /logout
                .logout()
                .logoutSuccessHandler(customLogoutSuccessHandler)
                // 無效會(huì)話
                .invalidateHttpSession(true)
                // 清除身份驗(yàn)證
                .clearAuthentication(true)
                .permitAll()
                ...;
    }
}

當(dāng)然,使用了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 會(huì)將資源服務(wù)器標(biāo)記為OAuth 2.0的客戶端, 它將負(fù)責(zé)將資源所有者(最終用戶)重定向到用戶必須輸入其憑據(jù)的授權(quán)服務(wù)器。完成后,用戶將被重定向回具有授權(quán)碼的客戶端。然后客戶端通過調(diào)用授權(quán)服務(wù)器獲取授權(quán)代碼并將其交換為訪問令牌。只有在此之后,客戶端才能使用訪問令牌調(diào)用資源服務(wù)器。
  • @EnableResourceServer 意味著所屬的服務(wù)需要訪問令牌才能處理請(qǐng)求。在調(diào)用資源服務(wù)器之前,需要先從授權(quán)服務(wù)器獲取訪問令牌。
  • 在資源服務(wù)器中配置的路徑,都會(huì)被 OAuth2AuthenticationProcessingFilter 處理,獲取token。
  • 之前一直在糾結(jié),客戶端獲取到了token,為什么在訪問 /user 的請(qǐng)求頭中并沒有Authorization,亦可請(qǐng)求成功。其實(shí)都因?yàn)镾ecurity。沒有在資源服務(wù)器中配置的路徑,登錄認(rèn)證成功后并不需要攜帶token,而還是使用Security需要的Cookie和Session。
  • 如果資源服務(wù)器沒有配置tokenService,就會(huì)調(diào)用配置的userInfoUri去auth-server獲取用戶信息;如果資源服務(wù)器配置了tokenService,再加上有UserDetails的實(shí)現(xiàn)類,可以解析,就不用在調(diào)用auth-server的接口。

參考:

Spring Security Oauth2和Spring Boot實(shí)現(xiàn)單點(diǎn)登錄
Spring Security Oauth2 單點(diǎn)登錄案例實(shí)現(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注解實(shí)現(xiàn)原理
Spring Security OAuth2 使用Redis存儲(chǔ)token鍵值詳解
Spring Security OAuth2實(shí)現(xiàn)使用JWT
jwt 官網(wǎng)
jwt 解碼器

到此這篇關(guān)于SpringSecurity OAuth2單點(diǎn)登錄和登出的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)SpringSecurity OAuth2單點(diǎn)登錄登出內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringSecurity-2.7中跨域問題解析

    SpringSecurity-2.7中跨域問題解析

    這篇文章主要介紹了SpringSecurity-2.7中跨域問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • 解決spring AOP中自身方法調(diào)用無法應(yīng)用代理的問題

    解決spring AOP中自身方法調(diào)用無法應(yīng)用代理的問題

    這篇文章主要介紹了解決spring AOP中自身方法調(diào)用無法應(yīng)用代理的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Swagger異常定位紀(jì)實(shí)Swagger設(shè)計(jì)問題分析

    Swagger異常定位紀(jì)實(shí)Swagger設(shè)計(jì)問題分析

    這篇文章主要為大家介紹了Swagger異常定位紀(jì)實(shí)Swagger設(shè)計(jì)的問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-02-02
  • Java中實(shí)現(xiàn)在一個(gè)方法中調(diào)用另一個(gè)方法

    Java中實(shí)現(xiàn)在一個(gè)方法中調(diào)用另一個(gè)方法

    下面小編就為大家分享一篇Java中實(shí)現(xiàn)在一個(gè)方法中調(diào)用另一個(gè)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • 簡單了解Spring Cloud Alibaba相關(guān)知識(shí)

    簡單了解Spring Cloud Alibaba相關(guān)知識(shí)

    這篇文章主要介紹了簡單了解Spring Cloud Alibaba相關(guān)知識(shí),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • java實(shí)現(xiàn)稀疏矩陣的壓縮與解壓的方法

    java實(shí)現(xiàn)稀疏矩陣的壓縮與解壓的方法

    這篇文章主要介紹了java實(shí)現(xiàn)稀疏矩陣的壓縮與解壓 ,把該稀疏矩陣壓縮以三元組形式表示并以文件形式保存,再寫另一個(gè)程序讀取文件中的信息把壓縮后的三元組還原成原來的稀疏矩陣,需要的朋友可以參考下
    2022-03-03
  • Spring Boot @Conditional注解使用示例詳解

    Spring Boot @Conditional注解使用示例詳解

    在SpringBoot中,@Conditional注解用于條件性地注冊(cè)bean,根據(jù)某些條件決定是否創(chuàng)建特定bean,可以實(shí)現(xiàn)Condition接口并重寫matches方法來定義條件,本文介紹Spring Boot @Conditional注解的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2024-12-12
  • maven項(xiàng)目下solr和spring的整合配置詳解

    maven項(xiàng)目下solr和spring的整合配置詳解

    這篇文章主要介紹了maven項(xiàng)目下solr和spring的整合配置詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-11-11
  • 詳解Springboot 注入裝配到IOC容器方式

    詳解Springboot 注入裝配到IOC容器方式

    今天通過實(shí)例代碼給大家介紹了Springboot 注入裝配到IOC容器方式,代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,感興趣的朋友跟隨小編一起看看吧
    2021-10-10
  • Centos中安裝jdk案例講解

    Centos中安裝jdk案例講解

    這篇文章主要介紹了Centos中安裝jdk案例講解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08

最新評(píng)論

富阳市| 镇安县| 黄石市| 乐陵市| 揭东县| 民权县| 务川| 红安县| 高唐县| 长春市| 攀枝花市| 深州市| 蒙山县| 封丘县| 平顶山市| 洪雅县| 连平县| 巴东县| 阿合奇县| 望奎县| 九台市| 岳池县| 方城县| 景泰县| 雅江县| 中江县| 时尚| 钟祥市| 得荣县| 汉沽区| 溧阳市| 泸溪县| 获嘉县| 赤城县| 广南县| 石景山区| 永登县| 吉林省| 施甸县| 麻江县| 汝阳县|